/*
 * The MIT License
 *
 * Copyright 2012 Anonymous.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package pentomino.ui;

import java.awt.*;
import java.awt.event.KeyEvent;
import javax.swing.JPanel;
import pentomino.logic.Figure;
import pentomino.logic.PentominoGame;
import pentomino.logic.Playground;

/**
 *
 * Represents a user control that shows the game field state.
 */
public class PlaygroundPanel extends JPanel {
    
    private PentominoGame game;
    private Playground gameField;
    private boolean drawTool;
    private Rectangle workingArea;
    private Rectangle elemenentBounds;
    
    // Mouse current coordinates:
    private int cx;
    private int cy;
    
    /*============== Constructors ==============*/
    
    /**
     * Initializes a new instance of the PlaygroundPanel class by the
     * specified game field.
     * 
     * @param game a pentomino game logic 
     */
    public PlaygroundPanel(PentominoGame game)
    {        
        this.game = game;
        this.gameField = game.getPlayground();
        this.drawTool = false;
        recalcBounds();
    }
    
    /*============== Member access ==============*/
    
    /**
     * Sets the tool drawing mode (hide/show).
     * 
     * @param value a tool drawing state
     */
    public void setToolVisible(boolean value)
    {
        if (drawTool != value)
        {
            drawTool = value;
            repaint();
            
            if (drawTool)
            {
                cx = workingArea.x + 1;
                cy = workingArea.y + 1;
            }
        }
    }
    
    /**
     * Sets the current mouse location.
     * 
     * @param x x-coord of the mouse location
     * @param y y-coord of the mouse location
     */
    public void setMouseLocation(int x, int y)
    {
        this.cx = x;
        this.cy = y;
    }
    
    /**
     * Moves the target.
     * 
     * @param orientation a moving orientation
     */
    public void moveTarget(int orientation)
    {
        if (cx == 0 && cy == 0)
        {
            cx = workingArea.x + 1;
            cy = workingArea.y + 1;
        }
        
        switch (orientation)
        {
            case KeyEvent.VK_LEFT:
                if (cx - elemenentBounds.width > workingArea.x)
                    cx -= elemenentBounds.width;
                break;
            case KeyEvent.VK_RIGHT:
                if (cx + elemenentBounds.width < workingArea.x + workingArea.width + 2)
                    cx += elemenentBounds.width;
                break;
            case KeyEvent.VK_UP:
                if (cy - elemenentBounds.height > workingArea.y)
                    cy -= elemenentBounds.height;
                break;
            case KeyEvent.VK_DOWN:
                if (cy +elemenentBounds.height < workingArea.y + workingArea.height + 2)
                    cy += elemenentBounds.height;
                break;
        }
        
        repaint();
    }
    
    /*============== Methods ==============*/
    
    /**
     * Updates the control content when a new game has been started.
     * 
     * @param newGame a new game content 
     */
    public void update(PentominoGame newGame)
    {
        if (newGame == null)
            throw new IllegalArgumentException("A 'newGame' argument can not be a null.");
        
        this.gameField = newGame.getPlayground();
        this.game = newGame;
        this.drawTool = false;
        recalcBounds();
    }
    
    /**
     * Indicates when the mouse cursor over the game field.
     * 
     * @param mx a mouse x-coord
     * @param my a mouse y-coord
     * @return true if the mouse on the game field
     */
    private boolean isMouseOnField(int mx, int my)
    {
        return workingArea.contains(mx, my);
    }
    
    /**
     * Tries to insert the selected figure.
     * 
     * @param x x-coord of the mouse location
     * @param y y-coord of the mouse location
     * @return insertion success
     */
    public boolean doInsertAction(int x, int y)
    {
        cx = x;
        cy = y;
        
        if (isMouseOnField(cx, cy))
        {
            Point location = translateLocation(cx, cy);
            
            if (game.canInsertTo(location.x, location.y))
            {
                game.insertCurrent(location.x, location.y);
                game.setCurrentFigure(null);
                
                return true;
            }
        }
        
        repaint();
        
        return false;
    }
    
    /**
     * Tries to insert the selected figure.
     * 
     * @return insertion success 
     */
    public boolean doInsertAction()
    {
        return doInsertAction(cx, cy);
    }
    
    /**
     * Tries to remove a figure at specified location.
     * 
     * @param x x-coord of the mouse location
     * @param y y-coord of the mouse location
     * @return removing success
     */
    public final boolean doRemoveAction(int x, int y)
    {
        cx = x;
        cy = y;

        // It's possible only when no one figure is selected.
        if (isMouseOnField(cx, cy) && game.getCurrentFigure() == null)
        {
            Point location = translateLocation(cx, cy);
            
            game.removeAt(location.x, location.y);
            
            return true;
        }
        
        repaint();
        
        return false;
    }
    
    /**
     * Updates the control state while mouse moving.
     * 
     * @param x x-coord of the mouse location
     * @param y y-coord of the mouse location
     */
    public final void doMoveAction(int x, int y)
    {
        cx = x;
        cy = y;
        drawTool = isMouseOnField(cx, cy);
        
        repaint();
    }
    
    /**
     * Translates the coordinates from screen to array units.
     * 
     * @param sx a screen x-coord
     * @param sy a screen y-coord
     * @return position in the game field array
     */
    private Point translateLocation(int sx, int sy)
    {
        Point result = new Point();
        
        sx -= workingArea.x;
        sy -= workingArea.y;
        
        result.x = sx / elemenentBounds.width;
        result.y = sy / elemenentBounds.height;
        
        return result;
    }
    
    /**
     * Calculates a game field bounds on the control.
     * 
     * @return rectangle - a game field bounds
     */
    private void recalcBounds()
    {
        int pwidth = getWidth();
        int pheight = getHeight();
        
        int md = pwidth > pheight ? pwidth : pheight;
        
        md -= 40;
        
        elemenentBounds = new Rectangle();
        elemenentBounds.width = md / gameField.getWidth();
        elemenentBounds.height = md / gameField.getWidth();
        elemenentBounds.x = pwidth / 2 - elemenentBounds.width * gameField.getWidth() / 2;
        elemenentBounds.y = pheight / 2 - elemenentBounds.height * gameField.getHeight() / 2;
        
        workingArea = new Rectangle();
        workingArea.x = elemenentBounds.x;
        workingArea.y = elemenentBounds.y;
        workingArea.width = elemenentBounds.width * gameField.getWidth();
        workingArea.height = elemenentBounds.height * gameField.getHeight();
    }
    
    /**
     * Draws a panel content.
     * 
     * @param g the Graphics context in which to paint
     */
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        Graphics2D g2d = (Graphics2D)g;
        
        Color figureColor;
        Color borderColor = InterfaceHelper.BORDER_COLOR;
        
        recalcBounds();
        
        Rectangle rect = new Rectangle(elemenentBounds);     
        Color c = g.getColor();
        
        GradientPaint gradient = new GradientPaint(workingArea.x, workingArea.y, Color.WHITE, 
                                                   workingArea.width, workingArea.height, SystemColor.control);
        
        
        
        g2d.setPaint(gradient);
        g2d.fillRect(workingArea.x, workingArea.y, workingArea.width, workingArea.height);
                
        for (int j = 0; j < gameField.getHeight(); j++)
        {
            rect.x = elemenentBounds.x;

            for (int i = 0; i < gameField.getWidth(); i++)
            {
                char figureType = gameField.figureAt(i, j);
                
                figureColor = InterfaceHelper.getFigureColor(figureType);
                
                g2d.setColor(figureColor);
                g2d.fillRect(rect.x, rect.y, rect.width, rect.height);     
                g2d.setColor(borderColor);
                g2d.drawRect(rect.x, rect.y, rect.width, rect.height);
                                
                rect.x += rect.width;
            }
            
            rect.y += rect.height;
        }
        
        /* Tool (selected figure) drawing. */
        
        Figure f = game.getCurrentFigure();
        
        if (drawTool && f != null)
        {            
            Point arrayloc = translateLocation(cx, cy);
            
            if (gameField.tryInsert(f, arrayloc.x, arrayloc.y))
                figureColor = InterfaceHelper.getFigureColor(f);
            else
                figureColor = InterfaceHelper.INVALID_COLOR;
            
            arrayloc.x = arrayloc.x - 2;
            arrayloc.y = arrayloc.y - 2;
            
            rect = new Rectangle(elemenentBounds);
            
            rect.x = elemenentBounds.x + arrayloc.x * rect.width;
            rect.y = elemenentBounds.y + arrayloc.y * rect.height;
            
            for (int i = 0; i < f.getRows(); i++)
            {
                rect.x = elemenentBounds.x + arrayloc.x * rect.width;
                
                for (int j = 0; j < f.getColumns(); j++)
                {
                    byte b = f.bitAt(i, j);
                    
                    if (b == 1)
                    {
                        g2d.setColor(figureColor);
                        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
                        g2d.fillRect(rect.x, rect.y, rect.width, rect.height);
                        g2d.setColor(borderColor);
                        g2d.drawRect(rect.x, rect.y, rect.width, rect.height);
                    }
                    
                    rect.x += rect.width;
                }
                
                rect.y += rect.height;
            }
        }
            
        g.setColor(c);
    }
}
